Skip to content

MCP: admit calls chained on another's end, match only in-range stdio ids - #1666

Merged
jlouvel merged 3 commits into
3.0from
fix/mcp-workers-sequential-sends
Nov 10, 2026
Merged

jlouvel merged 3 commits into
3.0from
fix/mcp-workers-sequential-sends

Conversation

@jlouvel

@jlouvel jlouvel commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

This fixes two issues Copilot found in #1665 after it was merged.

Chained MCP calls refused with one slot (a regression in #1665).

  • MCP 2026-07-28: resources, prompts, subscriptions/listen and stdio #1665 made McpWorkers free a call's admission permit only after signalling the call's end. That stopped stop() from writing an error after a listen stream's result.
  • But a subscriber that starts another call from that end signal ran while the permit was still held. With a call limit of 1, sendMessage(a).then(sendMessage(b)) had its second send refused. That affects the HTTP and 2025-11-25 stdio client transports, which share McpWorkers.
  • The permit is now freed before the signal, so a chained call is admitted. A separate count tracks calls until their end's subscribers are done, and awaitIdle() waits on that count, so the listen-stream shutdown fix still holds.
  • A call failing with an Error also ends with it and leaves that count, so awaitIdle() doesn't time out afterwards (found by Copilot here).

An out-of-range numeric id cancelling another request over stdio.

  • Matching a notifications/cancelled to its request converted numeric ids through long. 9223372036854775808.0 therefore matched 9223372036854775807.
  • A numeric id now becomes a long only when it is exactly an integer in the long range. Any other number is compared as a normalised BigDecimal.
  • A floating-point id is taken at its exact binary value, not its shortest decimal form, so 1000000000000000128.0 matches request 1000000000000000128 (found by Copilot here).

Tests.

  • The new McpWorkersTestCase:
    • a call chained on the end of another is admitted with one slot. This test fails on 3.0 and passes with the fix;
    • awaitIdle() waits until a call's end-signal subscribers are done;
    • a call failing with an Error ends with it and no longer counts as running.
  • McpModernStdioTestCase:
    • a cancel naming 9223372036854775808.0 cancels nothing, then one naming 9223372036854775807 cancels that request;
    • a cancel naming 1000000000000000128.0 cancels request 1000000000000000128.

mvn -pl org.restlet.ext.mcp verify: 73 tests, coverage and SpotBugs pass.

🤖 Generated with Claude Code

McpWorkers frees a call's permit before signalling its end again, so a
call its subscribers start then is admitted with a single slot; a
separate count keeps the call running until they are done, which
awaitIdle() waits on, so stop() still ends listen streams cleanly. Over
stdio, a numeric request id is a long only when it is exactly an integer
in range, so an out-of-range cancellation no longer matches the largest
long.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@jlouvel
jlouvel requested a balanced review from Copilot November 9, 2026 21:07
@jlouvel jlouvel self-assigned this Nov 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Lossy numeric ID conversion can cancel the wrong request, while worker errors can permanently inflate the running count and cause subsequent awaitIdle() calls to time out.

2 open issues
What changed in this PR

This PR addresses two MCP transport issues following #1665: chained calls rejected at the call limit and out-of-range stdio cancellation IDs matching unrelated requests.

Changes:

  • Separates admission permits from worker completion tracking so chained calls can proceed while shutdown waits for end-signal subscribers.
  • Uses decimal-based numeric ID normalization for stdio cancellation matching.
  • Adds regression tests for chained calls, idle waiting, and out-of-range cancellation IDs.
File Description
org.restlet.ext.mcp/​src/​test/​java/​org/​restlet/​ext/​mcp/​McpWorkersTestCase.java Adds chained-call and idle-wait regression tests.
org.restlet.ext.mcp/​src/​test/​java/​org/​restlet/​ext/​mcp/​McpModernStdioTestCase.java Tests cancellation IDs beyond the long range.
org.restlet.ext.mcp/​src/​main/​java/​org/​restlet/​ext/​mcp/​StdioServerHelper.java Changes numeric cancellation-key normalization.
org.restlet.ext.mcp/​src/​main/​java/​org/​restlet/​ext/​mcp/​internal/​McpWorkers.java Releases admission before signalling and tracks completion separately.

Review effort: Balanced

A floating-point request id is compared by its exact binary value, not
its shortest decimal form, so 1000000000000000128.0 matches its request.
A call failing with an Error now ends with it and leaves the running
count, so awaitIdle() no longer times out afterwards; the Error is
rethrown once counted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The release notes are missing entries for the chained-call and stdio cancellation-ID fixes under the current development version’s “Bugs fixed” section.

1 open issue
2 resolved since last review

Review effort: Balanced

@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the MCP worker admission and numeric cancellation ID fixes.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain for the worker-admission and stdio cancellation-ID changes.

No open issues 🎉

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are reported for the chained-call admission and numeric cancellation-ID fixes.

No open issues 🎉

Review effort: Balanced

@jlouvel
jlouvel merged commit 35ef52e into 3.0 Nov 10, 2026
6 checks passed
@jlouvel
jlouvel deleted the fix/mcp-workers-sequential-sends branch November 10, 2026 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants